The package “ggplot2” has become the go-to package for making publication-quality plots. It is part of the tidyverse It can be complicated to use. It derived from the “grammar of graphics” concept that aims to have a consistent set of options to build a diverse set of simple or complex plots from data.
We will load ggplot2, which will automatically load a useful package of color palette, ‘RColorBrewer’. The packages ‘dplyr’, ‘forcats’ and ‘data.table’ are loaded since we will need to use some of their functions to prepare the data for plots.
The data set, “nass.hay” is from the ‘agridat’ package. It is historic data on hay production across U.S. states from 1909 to 2012 gathered by the National Agricultural Statistics Service. This script also creates a data object only containing data from Idaho, and a data object with hay production stats from 2012.
library(dplyr); library(data.table); library(forcats)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
##
## Attaching package: 'data.table'
## The following objects are masked from 'package:dplyr':
##
## between, first, last
library(ggplot2)
library(agridat) # loaded so we can access the following date set:
data("nass.hay")
str(nass.hay)
## 'data.frame': 5044 obs. of 4 variables:
## $ year : int 1909 1909 1909 1909 1909 1909 1909 1909 1909 1909 ...
## $ state: Factor w/ 49 levels "Alabama","Alaska",..: 1 3 4 5 6 7 8 9 10 11 ...
## $ acres: num 275000 98000 409000 2503000 1180000 ...
## $ yield: num 0.96 2.42 1.05 1.61 1.73 1.15 1.22 0.69 0.86 2.16 ...
head(nass.hay)
## year state acres yield
## 1 1909 Alabama 275000 0.96
## 2 1909 Arizona 98000 2.42
## 3 1909 Arkansas 409000 1.05
## 4 1909 California 2503000 1.61
## 5 1909 Colorado 1180000 1.73
## 6 1909 Connecticut 387000 1.15
hay_idaho <- filter(nass.hay, state == "Idaho")
hay2012 <- filter(nass.hay, year == 2012)
hay_pnw <- filter(nass.hay, state %in% c("Oregon", "Washington", "Montana","Idaho")) %>%
mutate(state = fct_drop(state))
# fct_drop() command run so extra factor levels in "state" that are not longer represented in "hay_pnw" are dropped
Note:
+ notation in their plots (see below). I think there is some regret over this style decision, but it is now an accepted convention!Let’s plot Yield over time for Idaho hay production
ggplot(hay_idaho)
(not much to see here - we have not specified the aesthetics or the geom)
ggplot(hay_idaho, mapping = aes(x = year, y = yield))
This step mapped “year” and “yield” from the hay_idaho data set to the x and y axes, respectively. Nothing is plotted because we have not specified what type of plot geom we want.
(explicit mapping command dropped - use a positional argument instead)
ggplot(hay_idaho, aes(x = year, y = yield)) +
geom_line()
Question: What happens if we use
%>% instead of +?
ggplot(hay_idaho, aes(x = year, y = yield)) +
geom_line() +
ggtitle("Total historic hay yields in Idaho") +
xlab("Year") +
ylab("yield (1000s of tons)")
(and other geom arguments)
ggplot(hay_idaho, aes(x = year, y = yield)) +
geom_line(color = "turquoise", size = 1.5, linetype = 1) +
ggtitle("Total historic hay yields in Idaho") +
xlab("Year") +
ylab("yield (1000's of tons)")
The default line type is “1”, which is a plain line. There are other types:
ggplot(hay_idaho, aes(x = year, y = yield)) +
geom_point(shape = 2, col = "navy") +
ggtitle("Total historic hay yields in Idaho") +
xlab("Year") +
ylab("yield (1000s of tons)")
There are many options for the type of plotting character (“pch” in base R plotting and “shape” in ggplot):
The default shape is “1” (the circle). Plotting symbols 21 - 25 have outline color and fill attributes, while the other symbols only take a single color argument.
ggplot(hay_idaho, aes(x = year, y = yield)) +
geom_area(fill = "orange", col = "gray20") +
ggtitle("Total historic hay yields in Idaho") +
xlab("Year") +
ylab("yield (1000s of tons)")
ggplot(hay_pnw, aes(x = state, y = yield)) +
geom_boxplot() +
ggtitle("Yearly hay yields in the Pacific Northwest") +
xlab("State") +
ylab("yield (1000s of tons)")
Color all boxplots the same:
ggplot(hay_pnw, aes(x = state, y = yield)) +
geom_boxplot(fill = "yellow") +
ggtitle("Yearly hay yields in the Pacific Northwest") +
xlab("State") +
ylab("yield (1000s of tons)")
Color boxplots by state:
(This requires an aes statement.)
ggplot(hay_pnw, aes(x = state, y = yield, fill = state)) +
geom_boxplot() +
ggtitle("Yearly hay yields in the Pacific Northwest") +
xlab("State") +
ylab("yield (1000s of tons)")
Question: What happens if we specify “color = state” instead of “fill = state”?
ggplot(hay_idaho, aes(x = yield)) +
geom_histogram(fill = "gray70", col = "black", bins = 20)
## Warning: Removed 1 rows containing non-finite values (stat_bin).
Unlike in base R plotting, creating a density plot and shading the are under the curve is easy.
ggplot(hay_pnw, aes(x = yield, fill = state)) +
geom_density()
In this plot, it is hard to see what is going on since the each distribution covers up the one plotted before it (so we can’t really see Idaho, but Washington’s hay yield distribution is clear). The attribute “alpha” can be used to add a transparency to colors. The code is alpha = n where n is the proportion of the color that is shown (0 is completely transparent and 1 is no transparency).
ggplot(hay_pnw, aes(x = yield, fill = state)) +
geom_density(alpha = 0.5)
Geoms are best thought of as plotting layers. Additional geoms can be added to a single plot, adding additional layers of information. Each geom is added to the plot in the order it is specified in the ggplot statement.
Adding an overall of points over boxplots is a good way to summarise the empirical distribution of a large number of data points:
ggplot(hay_pnw, aes(x = state, y = yield, fill = state)) +
geom_boxplot() +
geom_point() +
ggtitle("Yearly hay yields in the Pacific Northwest") +
xlab("State") +
ylab("yield (1000s of tons)")
In this example, it is likely there is over-plotting of data since each state has 104 data point associated with it. The geom geom_jitter() can be used to jiggle overlapping points. Points can be jittered in the vertical and horizontal direction. In this case, jittering in the horizontal direction will result in no change in the interpretation since the y-axis indicates actual value associated with each point.
ggplot(hay_pnw, aes(x = state, y = yield, fill = state)) +
geom_boxplot() +
geom_jitter(width = 0.3, height = 0) +
ggtitle("Yearly hay yields in the Pacific Northwest") +
xlab("State") +
ylab("yield (1000s of tons)")
Sometimes, it is useful to create a set of similar plots with the same scaling. Facets are useful for this purpose.
For an example, below is a plot of the distributions from above using facet_grid().
ggplot(hay_pnw, aes(x = yield)) +
geom_density(col = "blue", fill = "dodgerblue", alpha = 0.6) +
facet_grid(. ~ state) # for a horizontal facet
ggplot(hay_pnw, aes(x = yield)) +
geom_density(col = "darkgreen", fill = "springgreen3", alpha = 0.6) +
facet_grid(state ~ .) # for a vertical facet
Faceting can also be done be used to create a grid of plots with facet_wrap():
ggplot(hay_pnw) +
geom_line(aes(x = year, y = yield), col = "darkcyan") +
facet_wrap(. ~ state, nrow = 2, ncol = 2)
Example: create a band indicates the minimum and maximum range of values
hay_yearly <- nass.hay %>% group_by(year) %>%
summarise(Yield = mean(yield), yield_min = min(yield), yield_max = max(yield))
ggplot(hay_yearly, aes(x = year)) +
geom_ribbon(aes(ymin = yield_min, ymax = yield_max), fill = "gray70") +
geom_line(aes(y = Yield), size = 0.8)
Example: add error bars
Two other plot adjustments included: * indicate the range of numbers that the y-axis should span * flip the x and y axes
hay_pnw_summary <- hay_pnw %>% group_by(state) %>%
summarise(Yield = mean(yield, na.rm = T), yield_sd = sd(yield, na.rm = T))
ggplot(hay_pnw_summary, aes(x = state)) +
geom_errorbar(aes(ymin = Yield - yield_sd, ymax = Yield + yield_sd), width = 0.2) +
geom_point(aes(y = Yield), shape = 18, size = 5, col = "blue") +
ylim(c(0, 3.7)) +
coord_flip()
P <- ggplot(hay_idaho, aes(x = acres, y = yield)) +
geom_point(shape = 16, alpha = 0.6, size = 2.5)
P # Plots can be assigned to an object and then later added to
P +
geom_smooth(method = "lm") +
geom_smooth(method = "loess", col = "yellowgreen")
theme() argument command.facet.plot <- ggplot(hay_pnw) +
geom_line(aes(x = year, y = yield), col = "darkcyan") +
facet_wrap(. ~ state, nrow = 2, ncol = 2)
facet.plot + theme_minimal()
This is not exactly what I want. Larger labels on everything would be helpful, as well as an outline around each plot. And while we are at it, labeling the x-axis for each decade rather than every 25 years would be easier to interpret.
facet.plot +
theme_minimal() + # this must be added to plot before manual theme adjustments
theme(
axis.title = element_text(size = 14),
axis.text = element_text(size = 13)
)
facet.plot +
scale_x_continuous(breaks = c(seq(1910, 2015, by = 10))) +
theme_minimal() +
theme(
axis.title = element_text(size = 14),
axis.text = element_text(size = 13)
)
It looks like the years are spaced too close together and there are too many grid lines. This can all be fixed.
facet.plot +
scale_x_continuous(breaks = c(seq(1910, 2015, by = 10))) +
theme_minimal() + # this must be added to plot before manual theme adjustments
theme(
axis.title = element_text(size = 14), # adsjut title of the axes
axis.text = element_text(size = 13), # adjust axes tick labels
axis.text.x = element_text(angle = 45, hjust = 1), # adjust individual axis tick labels
panel.grid.minor = element_blank(), # remove minor grid lines (the major grid lines were retained)
panel.border = element_rect(colour = "black", fill = "transparent"), # create a border around each panel
strip.text = element_text(size = 15) # adjust panel labels (e.g. "Idaho")
)
ggplot(hay_pnw) +
geom_line(aes(x = year, y = yield, col = state)) +
scale_x_continuous(breaks = c(seq(1910, 2015, by = 10))) +
theme_bw()
This looks okay, but it could be better. There is room to put the legend inside the plot and the text should probably be larger. Also, the legend title is not really needed.
ggplot(hay_pnw) +
geom_line(aes(x = year, y = yield, col = state)) +
scale_x_continuous(breaks = c(seq(1910, 2015, by = 10))) +
theme_bw() +
theme(
legend.position = c(0.15, 0.8), # give exact coordinates (ranging from 0 to 1)
legend.background = element_rect(colour = "black"),
legend.text = element_text(size = 12),
legend.title = element_blank()
)
The legend can also be moved to locations outside the main plotting area with arguments such as legend.position = "top", legend.position = "left" and so on.
There are many options for adjusting text. You you will inevitable need to consult help files at some point.
R has a rich set of colors and some excellent palettes that provide excellent contrast and/or work for color-blind individuals. Several are automatically loaded with ggplot2, such as the RColorBrewer:
library(RColorBrewer)
display.brewer.all()
The package ‘viridis’ also has some nice color palettes that are widely used and accepted. Information about color palettes have been comprehensively explored by Emil Hvitfeldt and worth a read.
ggsave() is the recommended approach.ggplot(hay_pnw) +
geom_line(aes(x = year, y = yield), col = "violetred") +
facet_wrap(. ~ state, nrow = 2, ncol = 2)
ggsave("PNW_hay_production.png", dpi = 400)
## Saving 7 x 5 in image
| geom | purpose |
|---|---|
geom_violin() |
violin plot |
geom_ribbon() |
horizontal filled undulating band |
geom_bar() |
for creating bar plots |
geom_abline(), geom_hline(), geom_vline() |
for adding straight lines to a plot |
geom_map() |
part of the rich ecosystem of ggplot mapping functions |
geom_rug() |
adds rug plots to edges of a plot |
Many packages have been developed to extend ggplot’s functionality. Here are a few notable examples:
# break hay data into decennial increments (roughly)
nass.hay$decade <- cut(nass.hay$year, breaks = c(1900, 1920, 1930, 1940, 1950, 1960, 1970, 1980, 1990, 2000, 2020), labels = c("pre-1920", "1920s", "1930s", "1940s", "1950s", "1960s", "1970s", "1980s", "1990s", "2000s"))
library(ggridges); library(viridis)
##
## Attaching package: 'ggridges'
## The following object is masked from 'package:ggplot2':
##
## scale_discrete_manual
## Loading required package: viridisLite
ggplot(nass.hay, aes(x = yield, y = reorder(decade, desc(decade)), fill = ..x..)) +
geom_density_ridges_gradient(rel_min_height = 0.001) +
scale_fill_viridis(direction = -1) +
ylab("") # suppress the y-axis label
## Picking joint bandwidth of 0.129
library(plotly)
##
## Attaching package: 'plotly'
## The following object is masked from 'package:ggplot2':
##
## last_plot
## The following object is masked from 'package:stats':
##
## filter
## The following object is masked from 'package:graphics':
##
## layout
pl <- ggplot(nass.hay, aes(x = acres, y = yield, color = year)) +
geom_point() +
geom_point(aes(text = state)) +
#, shape = 21, col = "gray30", alpha = 0.6) +
#scale_fill_manual(values = alpha(rainbow(nlevels(nass.hay$state)), 0.5))
scale_color_gradientn(colors = alpha(rainbow(20), 0.5))
ggplotly(pl)
To save an interactive plot, use htmlwidgets::saveWidget().
vignette("ggplot2")
(there are innumerable ggplot tutorials on the interwebs)
Statistical consulting to support the College of Agriculture and Life Sciences.
Bill Price, Director, bprice@uidaho.edu, AgSci307
Julia Piaskowski, jpiaskowski@uidaho.edu, AgSci 305